package com.jxpanda.infrastructure.crypto.algorithm.signer;

import com.jxpanda.infrastructure.crypto.algorithm.signer.contract.AbstractSigner;
import com.jxpanda.infrastructure.crypto.constant.AlgorithmSign;
import com.jxpanda.infrastructure.crypto.model.BinaryData;

import java.security.*;
import java.util.Objects;

public class SignatureSigner extends AbstractSigner {

    private final PublicKey publicKey;

    private final PrivateKey privateKey;

    public SignatureSigner(AlgorithmSign.Signature signature, KeyPair keyPair) {
        super(signature.getAlgorithm());
        this.publicKey = keyPair.getPublic();
        this.privateKey = keyPair.getPrivate();
    }


    public SignatureSigner(AlgorithmSign.Signature signature, PublicKey publicKey) {
        super(signature.getAlgorithm());
        this.publicKey = publicKey;
        this.privateKey = null;
    }

    public SignatureSigner(AlgorithmSign.Signature signature, PrivateKey privateKey) {
        super(signature.getAlgorithm());
        this.privateKey = privateKey;
        this.publicKey = null;
    }

    public SignatureSigner(AlgorithmSign.Signature signature, PublicKey publicKey, PrivateKey privateKey) {
        super(signature.getAlgorithm());
        this.publicKey = publicKey;
        this.privateKey = privateKey;
    }

    @Override
    public BinaryData sign(BinaryData contentData) {
        Objects.requireNonNull(privateKey, "The PrivateKey must not be null.");
        try {
            Signature signature = Signature.getInstance(getAlgorithm());
            signature.initSign(privateKey);
            signature.update(contentData.bytes());
            return new BinaryData(signature.sign());
        } catch (InvalidKeyException | SignatureException | NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean verify(BinaryData contentData, BinaryData signatureData) {
        Objects.requireNonNull(publicKey, "The PublicKey must not be null.");
        try {
            Signature signature = Signature.getInstance(getAlgorithm());
            signature.initVerify(publicKey);
            signature.update(contentData.bytes());
            return signature.verify(signatureData.bytes());
        } catch (InvalidKeyException | SignatureException | NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    public static Builder builder(AlgorithmSign.Signature signature) {
        return new Builder(signature);
    }

    public static class Builder {
        private final AlgorithmSign.Signature signature;
        private KeyPair keyPair;
        private PublicKey publicKey;
        private PrivateKey privateKey;

        public Builder(AlgorithmSign.Signature signature) {
            this.signature = signature;
        }

        public Builder keyPair(KeyPair keyPair) {
            this.keyPair = keyPair;
            return this;
        }

        public Builder publicKey(PublicKey publicKey) {
            this.publicKey = publicKey;
            return this;
        }

        public Builder privateKey(PrivateKey privateKey) {
            this.privateKey = privateKey;
            return this;
        }

        public SignatureSigner build() {
            if (keyPair != null) {
                return new SignatureSigner(signature, keyPair);
            }
            if (publicKey != null && privateKey != null) {
                return new SignatureSigner(signature, publicKey, privateKey);
            }
            if (publicKey != null) {
                return new SignatureSigner(signature, publicKey);
            }
            if (privateKey != null) {
                return new SignatureSigner(signature, privateKey);
            }
            throw new IllegalArgumentException("The KeyPair, PublicKey and PrivateKey can not be null at the same time");
        }

        public SignatureSigner buildDefault() {
            return new SignatureSigner(signature, signature.keyProvider().generateKey());
        }


    }

}
